﻿using System;
using System.Collections.Generic;
using System.IO.Packaging;
using ICSharpCode.SharpZipLib.Zip;
using OPCDiffCore.Zip;

namespace OPCDiffCore
{
    class PhysicalAnalyzeModule : IAnalyzeModule
    {
        AnalyzeLevel IAnalyzeModule.GetCheckType()
        {
            return AnalyzeLevel.Physical;
        }

        PackageDiff IAnalyzeModule.RunAnalyze(AnalyzeConfiguration configuration)
        {
            return new PackageDiff(CheckDiff(configuration.OriginalFile,
                configuration.CompareFile));
        }

        private PartDiffCollection CheckDiff(string originalFile, string diffFile)
        {
            PartDiffCollection retCollection = new PartDiffCollection();

            // Open the zip files
            ZipFile originalZipFile, diffZipFile;
            try
            {
                originalZipFile = new ZipFile(originalFile);
                diffZipFile = new ZipFile(diffFile);
            }
            catch (Exception e)
            {
                throw new ArgumentException("The specified file can't be open: " + e.Message);
            }

            List<ZipEntry> removedEntries = new List<ZipEntry>();
            List<ZipEntry> newEntries = new List<ZipEntry>();

            // Compare ZipEntry
            bool found = false, modified = false;
            foreach (ZipEntry origEntry in originalZipFile)
            {
                foreach (ZipEntry diffEntry in diffZipFile)
                {
                    if (origEntry.Name == diffEntry.Name &&
                        origEntry.Size == diffEntry.Size &&
                        origEntry.Crc == diffEntry.Crc &&
                        origEntry.DateTime == origEntry.DateTime)
                    {
                        found = true;
                        break;
                    }

                    // Check for modified part
                    if (origEntry.Name == diffEntry.Name)
                    {
                        // We detect a modification, build the diff element object and add it to the return diff collection.
                        retCollection.Add(BuildModifiedZipPartDiff(origEntry, diffEntry));
                        modified = true;
                        break;
                    }
                }

                if (found)
                    found = false;
                else if (modified)
                    modified = false;
                else
                    removedEntries.Add(origEntry);
            }

            // Search new entries
            foreach (ZipEntry diffEntry in diffZipFile)
            {
                foreach (ZipEntry origEntry in originalZipFile)
                {
                    if (origEntry.Name == diffEntry.Name)
                    {
                        found = true;
                        break;
                    }
                }

                if (found)
                    found = false;
                else
                    newEntries.Add(diffEntry);
            }

            // Add remove parts
            foreach (ZipEntry removePart in removedEntries)
            {
                PartDiff partDiff = new PartDiff(new Uri("/" + removePart.Name, UriKind.Relative),
                    "", DiffType.Removed);
                retCollection.Add(partDiff);
            }

            // Add new parts
            foreach (ZipEntry newPart in newEntries)
            {
                PartDiff partDiff = new PartDiff(new Uri("/" + newPart.Name, UriKind.Relative),
                    "", DiffType.Added);
                retCollection.Add(partDiff);
            }

            return retCollection;
        }

        private PartDiff BuildModifiedZipPartDiff(ZipEntry removeOrModifiedEntry, ZipEntry diffEntry)
        {
            PartDiff retPartDiff = new PartDiff(
                new Uri("/" + removeOrModifiedEntry.Name, UriKind.Relative),
                "", DiffType.Modified);

            // Size
            if (removeOrModifiedEntry.Size != diffEntry.Size)
            {
                DiffElement diffElem = new DiffElement(DiffType.Modified,
                    DiffElementType.ZipEntry,
                    "The zip entry Size change");
                retPartDiff.Add(diffElem);
            }

            // CRC
            if (removeOrModifiedEntry.Crc != diffEntry.Crc)
            {
                DiffElement diffElem = new DiffElement(DiffType.Modified,
                    DiffElementType.ZipEntry,
                    "The zip entry CRC change");
                retPartDiff.Add(diffElem);
            }

            // Date Time
            if (removeOrModifiedEntry.DateTime != diffEntry.DateTime)
            {
                if (removeOrModifiedEntry.Size == diffEntry.Size &&
                removeOrModifiedEntry.Crc == diffEntry.Crc)
                {
                    DiffElement diffElem = new DiffElement(DiffType.Modified,
                        DiffElementType.ZipEntry,
                        "The zip entry date is different BUT the CRC and Size are not difference, this part may not have change");
                    retPartDiff.Add(diffElem);
                }
                else
                {
                    DiffElement diffElem = new DiffElement(DiffType.Modified,
                    DiffElementType.ZipEntry,
                    "The zip entry Date change");
                    retPartDiff.Add(diffElem);
                }
            }

            return retPartDiff;
        }
    }
}
